package viewer;

import javax.swing.*;
import javax.swing.event.*;

import model.GeometryModel3D;
import model.RoffLine2D;
import model.RoffTriangle;
import model.RoffTriangleManager;
import model.RoffVector3D;
import model.TrianglePixelization;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.geom.*;
import java.io.*;

public class RoffViewPanel extends JPanel implements MouseInputListener {

    public static final int VIEW_WIREFRAME                  = 0;
    public static final int VIEW_WIREFRAME_AA               = 1;
    public static final int VIEW_WIREFRAME_NOHIDDENLINES    = 2;
    public static final int VIEW_WIREFRAME_NOHIDDENLINES_AA = 3;
    public static final int VIEW_SOLID                      = 4;
    public static final int VIEW_POINTS                     = 5;

    public static final int CONTROL_VIEWING = 0;
    public static final int CONTROL_EDITING = 1;

    public static final int PS_NOBG = 1;
    public static final int PS_GRAY = 2;

    private GeometryModel3D shape3D;
    private Color bgColor;
    private int controlStyle;
    private int viewStyle;
    private int selectedPoint;

    private boolean lMouseDown;
    private boolean rMouseDown;
    private Point   lastClickPoint;

    private Dimension currentSize;

    private int numPoints;
    private int[] convertedXPoints;
    private int[] convertedYPoints;

    private int numLines2D;
    private RoffLine2D[] lines2D;

    private BufferedImage       buffer;
    private RoffPerspective     transformer;
    private RoffPhong           illuminator;
    private RoffTriangleManager tmanager;

    private RoffPerspective observedPerspective;
    TrianglePixelization p=new TrianglePixelization();
    PixelizationListener l=new PixelizationListener(p);
    private boolean observing;

    private double cullDepth;

    public RoffViewPanel() {

        transformer   = new RoffPerspective();
        illuminator   = new RoffPhong();
        tmanager      = new RoffTriangleManager();
        shape3D           = null;
        bgColor       = Color.lightGray;
        viewStyle     = VIEW_WIREFRAME;
        controlStyle  = CONTROL_VIEWING;
        numPoints     = 0;
        numLines2D    = 0;
        lines2D       = null;
        selectedPoint = -1;

        lMouseDown = false;
        rMouseDown = false;

        currentSize = getSize();

        cullDepth = 0.5;

        observedPerspective = null;
        observing = false;

        buffer = new BufferedImage(250, 250, BufferedImage.TYPE_INT_ARGB);
        this.setMinimumSize(new Dimension(250, 250));
        this.setPreferredSize(new Dimension(250, 250));
        this.setBackground(Color.white);
        addMouseListener(this);
        addMouseMotionListener(this);        

    }

    RoffPerspective getPerspective() {
        return transformer;
    }

    RoffPhong getLightingModel() {
        return illuminator;
    }

    void setLightingModel(RoffPhong newPhong) {
        illuminator = newPhong;
    }

    public int getNumberOfTriangles() {
        return tmanager.getNumberOfTriangles();
    }

    public Color getBackgroundColor() {
        return bgColor;
    }

    public void setBackgroundColor(Color newColor) {
        bgColor = newColor;
        buildBuffer();
        repaint();
    }

    public void setPolygonCullDepth(double newDepth) {
        cullDepth = newDepth;
    }

    public double getPolygonCullDepth() {
        return cullDepth;
    }

    public void setOff(GeometryModel3D newOff) {
        this.shape3D = newOff;
        controlStyle = CONTROL_VIEWING;
        //lmouseDown = false;
        //rmouseDown = false;
        selectedPoint = -1;

        numLines2D = shape3D.numUniqueEdges;
        lines2D = new RoffLine2D[numLines2D];
        for (int i = 0; i < numLines2D; i++) {
            lines2D[i] = new RoffLine2D();
        }
        numPoints = numLines2D * 2;
        convertedXPoints = new int[shape3D.numVertices];
        convertedYPoints = new int[shape3D.numVertices];

        tmanager.setPointArray(shape3D.vertices);
        tmanager.set2DPointArrays(convertedXPoints, convertedYPoints);
        tmanager.tessellate(shape3D.polygons);

        setDefaultPerspective();
    }

    public void fullPaint() {
        buildStructures();
        buildBuffer();
        repaint();
    }

    public void setViewingStyle(int newStyle) {
        if ((newStyle == VIEW_WIREFRAME) ||
            (newStyle == VIEW_WIREFRAME_AA) ||
            (newStyle == VIEW_WIREFRAME_NOHIDDENLINES) ||
            (newStyle == VIEW_WIREFRAME_NOHIDDENLINES_AA) ||
            (newStyle == VIEW_SOLID) ||
            (newStyle == VIEW_POINTS)) {
            viewStyle = newStyle;
            buildStructures();
            buildBuffer();
            repaint();
        }
    }

    public int getViewingStyle() {
        return viewStyle;
    }

    public void setControlStyle(int newStyle) {
        if ((newStyle == CONTROL_VIEWING) ||
            (newStyle == CONTROL_EDITING)) {
            controlStyle = newStyle;
            buildStructures();
            buildBuffer();
            repaint();
        }
    }

    public int getControlStyle() {
        return controlStyle;
    }

    public void setObservedPerspective(RoffPerspective toObserve) {
        if (toObserve != null) {
            observedPerspective = toObserve;
            observing = true;
        } else {
            observing = false;
            observedPerspective = null;
        }
    }

    public void setDefaultPerspective() {

        RoffVector3D newAt = null;

        double minX  = shape3D.minX;
        double maxX  = shape3D.maxX;
        double minY  = shape3D.minY;
        double maxY  = shape3D.maxY;
        double minZ  = shape3D.minZ;
        double maxZ  = shape3D.maxZ;

        double height = maxY - minY;
        double width  = maxX - minX;
        double depth  = maxZ - minZ;
        double maxDim = height;
        if (width > maxDim) {
            maxDim = width;
        }
        if (depth > maxDim) {
           maxDim = depth;
        }

        if (!observing) {
            newAt = new RoffVector3D((maxX + minX)/2.0,
                                   (maxY + minY)/2.0,
                                   (maxZ + minZ)/2.0);
            transformer.setAt(newAt);
            newAt.z = newAt.z + (maxDim * 2.5);
        } else {
            newAt = observedPerspective.getFrom();
            newAt = newAt.add(observedPerspective.getAt());
            maxDim = newAt.getLength();
            newAt = newAt.scalarDivide(2.0);
            transformer.setAt(newAt);
            newAt.x = newAt.x + (maxDim * 2.5);
            newAt.y = newAt.y + (maxDim / 2.0);
        }
        transformer.setFrom(newAt);
        transformer.setUp(new RoffVector3D(0, 1.0, 0));
        transformer.setAngle(45);
        fullPaint();
    }

    private void buildStructures() {

        RoffVector3D point2D;

        if (viewStyle == VIEW_POINTS ||
            viewStyle == VIEW_WIREFRAME_NOHIDDENLINES ||
            viewStyle == VIEW_WIREFRAME_NOHIDDENLINES_AA ||
            viewStyle == VIEW_SOLID ||
            ((viewStyle == VIEW_WIREFRAME || viewStyle == VIEW_WIREFRAME_AA) && controlStyle == CONTROL_EDITING)
            ) {
            for (int i = 0;i < shape3D.numVertices; i++) {
                point2D = transformer.WorldTo2D(shape3D.vertices[i]);
                convertedXPoints[i] = (int)(point2D.x);
                convertedYPoints[i] = (int)(point2D.y);
            }
        }
        if (viewStyle == VIEW_SOLID || 
            viewStyle == VIEW_WIREFRAME_NOHIDDENLINES ||
            viewStyle == VIEW_WIREFRAME_NOHIDDENLINES_AA) {
            tmanager.orderTriangles(transformer.getFrom(), transformer.getAt());
        }
        if (viewStyle == VIEW_WIREFRAME || viewStyle == VIEW_WIREFRAME_AA) {
            int line = 0;
            for (int i = 0; i < numPoints;i += 2) {
                transformer.clipToLine(shape3D.vertices[shape3D.getPointIndex(i)],
                                       shape3D.vertices[shape3D.getPointIndex(i + 1)],
                                       lines2D[line]);
                line++;
            }
        }
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Dimension testSize = getSize();
        if (!currentSize.equals(testSize)) {
            currentSize = testSize;
            transformer.setScreenScale(currentSize.width, currentSize.height);
            buffer = new BufferedImage(currentSize.width, currentSize.height, BufferedImage.TYPE_INT_ARGB);
            buildStructures();
            buildBuffer();
        }
        g.drawImage(buffer, 0, 0, null);
    }

    private void buildBuffer() {

        Graphics2D g = buffer.createGraphics();

        // Fill the background with the background color
        g.setPaint(bgColor);
        g.fill(new Rectangle(0, 0, currentSize.width, currentSize.height));

        if (observing) {
            RoffVector3D LoS = observedPerspective.getAt().sub(observedPerspective.getFrom());
            LoS = LoS.scalarDivide(5.0);
            double lenD = LoS.getLength();
            double lenS = lenD * Math.tan(observedPerspective.getAngle() * Math.PI / 360.0);
            RoffVector3D rightDir = LoS.cross(observedPerspective.getUp());
            rightDir.normalize();
            rightDir = rightDir.scalarMultiply(lenS * observedPerspective.getAspectRatio());
            RoffVector3D topDir = rightDir.cross(LoS);
            topDir.normalize();
            topDir = topDir.scalarMultiply(lenS);
            RoffLine2D line = new RoffLine2D();

            // Need the observed At, From, topLeft, topRight, bottomLeft, bottomRight
            RoffVector3D filmCenter = observedPerspective.getFrom().add(LoS);
            RoffVector3D topLeft3D = (filmCenter.add(topDir)).sub(rightDir);
            RoffVector3D topRight3D = (filmCenter.add(topDir)).add(rightDir);
            RoffVector3D bottomLeft3D = (filmCenter.sub(topDir)).sub(rightDir);
            RoffVector3D bottomRight3D = (filmCenter.sub(topDir)).add(rightDir);
            RoffVector3D oAt = observedPerspective.getAt();
            RoffVector3D oFrom = observedPerspective.getFrom();
            RoffVector3D oUp = observedPerspective.getUp();
            oUp = oUp.scalarMultiply(lenS);
            oUp = oUp.add(oAt);

            // Draw the line of sight
            GeneralPath gp = null;
            transformer.clipToLine(oAt, oFrom, line);
            if (line.valid) {
                gp = new GeneralPath();
                gp.moveTo(line.startX, line.startY);
                gp.lineTo(line.endX, line.endY);
                g.setPaint(Color.white);
                g.draw(gp);
            }

            // Draw the Up vector at the At point.
            transformer.clipToLine(oAt, oUp, line);
            if (line.valid) {
                gp = new GeneralPath();
                gp.moveTo(line.startX, line.startY);
                gp.lineTo(line.endX, line.endY);
                g.setPaint(Color.gray);
                g.draw(gp);
            }

            // Draw bulk of the viewing pyramid
            gp = new GeneralPath();
            transformer.clipToLine(topLeft3D, bottomLeft3D, line);
            if (line.valid) {
                gp.moveTo(line.startX, line.startY);
                gp.lineTo(line.endX, line.endY);
            }

            transformer.clipToLine(bottomLeft3D, oFrom, line);
            if (line.valid) {
                gp.moveTo(line.startX, line.startY);
                gp.lineTo(line.endX, line.endY);
            }

            transformer.clipToLine(oFrom, bottomRight3D, line);
            if (line.valid) {
                gp.moveTo(line.startX, line.startY);
                gp.lineTo(line.endX, line.endY);
            }

            transformer.clipToLine(bottomRight3D, topRight3D, line);
            if (line.valid) {
                gp.moveTo(line.startX, line.startY);
                gp.lineTo(line.endX, line.endY);
            }

            transformer.clipToLine(bottomLeft3D, bottomRight3D, line);
            if (line.valid) {
                gp.moveTo(line.startX, line.startY);
                gp.lineTo(line.endX, line.endY);
            }

            g.setPaint(Color.red);
            g.draw(gp);

            // Draw the top triangle of the viewing pyramid
            gp = new GeneralPath();
            transformer.clipToLine(oFrom, topLeft3D, line);
            if (line.valid) {
                gp.moveTo(line.startX, line.startY);
                gp.lineTo(line.endX, line.endY);
            }

            transformer.clipToLine(topLeft3D, topRight3D, line);
            if (line.valid) {
                gp.moveTo(line.startX, line.startY);
                gp.lineTo(line.endX, line.endY);
            }

            transformer.clipToLine(oFrom, topRight3D, line);
            if (line.valid) {
                gp.moveTo(line.startX, line.startY);
                gp.lineTo(line.endX, line.endY);
            }

            g.setPaint(Color.green);
            g.draw(gp);
        }
       
        if (shape3D.vertices!=null){
        	g.setPaint(Color.red);
        	RoffVector3D centeroid=shape3D.getCenteroid();
        	RoffVector3D c=transformer.WorldTo2D(centeroid); 
        	g.fillOval((int)c.x-5, (int)c.y-5, 10, 10);  
        }
       
        g.setPaint(illuminator.getOutsideColor());	
        if (viewStyle == VIEW_WIREFRAME) {
            GeneralPath gp = new GeneralPath();
            for (int i = 0; i < numLines2D; i++) {
                if (lines2D[i].valid) {
                    gp.moveTo(lines2D[i].startX, lines2D[i].startY);
                    gp.lineTo(lines2D[i].endX, lines2D[i].endY);
                }
            }
            g.draw(gp);
        } else if (viewStyle == VIEW_WIREFRAME_AA) {
            g.addRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
            GeneralPath gp = new GeneralPath();
            for (int i = 0; i < numLines2D; i++) {
                if (lines2D[i].valid) {
                    gp.moveTo(lines2D[i].startX, lines2D[i].startY);
                    gp.lineTo(lines2D[i].endX, lines2D[i].endY);
                }
            }
            g.draw(gp);
	} else if (viewStyle == VIEW_SOLID) {
	    int numTriangles = tmanager.getNumberOfTriangles();
	    RoffTriangle tri;
        Polygon poly;
        illuminator.setWorldLight(transformer.EyeToWorld(illuminator.getLightDirection()));
	    for (int i = 0;i < numTriangles;i++ ){
                tri = tmanager.get(i);
                if (tri.getDepth() > cullDepth) {
                    poly = tmanager.getPolygon(i);
                    //set the color to illuminator.Illuminate(tri->getNormal(), tri->getView());
                    g.setPaint(illuminator.illuminate(tri.getNormal(), tri.getView()));
                    g.fillPolygon(poly);
                }
	    }
	} else if (viewStyle == VIEW_WIREFRAME_NOHIDDENLINES) {
	    int numTriangles = tmanager.getNumberOfTriangles();
	    RoffTriangle tri;
            Polygon poly;
            Rectangle bounds;
            Rectangle outerBounds = new Rectangle(-500, -500, currentSize.width + 500, currentSize.height + 500);
            illuminator.setWorldLight(transformer.EyeToWorld(illuminator.getLightDirection()));
	    for (int i = 0;i < numTriangles;i++ ){
                tri = tmanager.get(i);
                l.clear(); 
                p.doPixelization2D(tri); 
                if (tri.getDepth() > cullDepth) {
                    poly = tmanager.getPolygon(i);
                    g.setPaint(bgColor);
                    g.fillPolygon(poly);
                    //set the color to illuminator.Illuminate(tri->getNormal(), tri->getView());
                    g.setPaint(illuminator.illuminate(tri.getNormal(), tri.getView()));
                    bounds = poly.getBounds();
                    if (outerBounds.contains(bounds)) {
                        g.drawPolygon(poly);
                        for (RoffVector3D point:l.getPoints()){
                        	RoffVector3D c=transformer.WorldTo2D(point); 
                        	g.fillOval((int)c.x-2, (int)c.y-2, 4, 4);
                        }
                    }
                }
	    }
	    /*
		int totalVertices = shape3D.numVertices;
        int xPoint;
        int yPoint;
		for (int i = 0;i < totalVertices;i++) {
            xPoint = convertedXPoints[i];
            yPoint = convertedYPoints[i];
            g.drawString(shape3D.vertices[i].x+","+shape3D.vertices[i].y+","+shape3D.vertices[i].z, xPoint, yPoint); 
		}
		*/
	} else if (viewStyle == VIEW_WIREFRAME_NOHIDDENLINES_AA) {
            g.addRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
	    int numTriangles = tmanager.getNumberOfTriangles();
	    RoffTriangle tri;
            Polygon poly;
            Rectangle bounds;
            Rectangle outerBounds = new Rectangle(-500, -500, currentSize.width + 500, currentSize.height + 500);
            illuminator.setWorldLight(transformer.EyeToWorld(illuminator.getLightDirection()));
	    for (int i = 0;i < numTriangles;i++ ){
                tri = tmanager.get(i);
                if (tri.getDepth() > cullDepth) {
                    poly = tmanager.getPolygon(i);
                    g.setPaint(bgColor);
                    g.fillPolygon(poly);
                    //set the color to illuminator.Illuminate(tri->getNormal(), tri->getView());
                    g.setPaint(illuminator.illuminate(tri.getNormal(), tri.getView()));
                    bounds = poly.getBounds();
                    if (outerBounds.contains(bounds)) {
                        g.drawPolygon(poly);
                    }
                }
	    }
	} else if (viewStyle == VIEW_POINTS) {
		// Draw all the points in the array.
		int totalVertices = shape3D.numVertices;
                final WritableRaster raster = buffer.getRaster();
                final ColorModel model = buffer.getColorModel();
                final Object colorData = model.getDataElements(illuminator.getOutsideColor().getRGB(), null);
                int xPoint;
                int yPoint;
		for (int i = 0;i < totalVertices;i++) {
                    xPoint = convertedXPoints[i];
                    yPoint = convertedYPoints[i];
                    if (xPoint > 0 && yPoint > 0 && xPoint < currentSize.width && yPoint < currentSize.height) {
                        raster.setDataElements(xPoint,yPoint,colorData);
                    }
		}
	}
	// If a point is selected, put a box around it.
	if (selectedPoint > -1) {
		int cornerX = convertedXPoints[selectedPoint] - 2;
		int cornerY = convertedYPoints[selectedPoint] - 2;
                g.setPaint(Color.white);
		g.draw(new Rectangle(cornerX, cornerY, 4, 4));
	}
    }

    public void mouseDragged(MouseEvent e) {

        Point point = e.getPoint();
        boolean redraw = false;

        lMouseDown = ((e.getModifiers() & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK);
        rMouseDown = ((e.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK);

        // If we are viewing: i.e. Rotating, zooming or shift-panning.
        if (controlStyle == CONTROL_VIEWING) {
            // If both buttons down,
            // shift pan along current "axes".
            if (rMouseDown && lMouseDown) {
                double xChange = 0;
                double yChange = 0;
                if (point.x < lastClickPoint.x - 3) {
                    xChange = -.05;
                } else if (point.x > lastClickPoint.x + 3) {
                    xChange = .05;
                }
                if (point.y < lastClickPoint.y - 3) {
                    yChange = -.05;
	    	} else if (point.y > lastClickPoint.y + 3) {
                    yChange = .05;
                }
                transformer.translate(xChange, yChange);
                redraw = true;
            // If only the right button, zoom in and out by 5%.
            } else if (rMouseDown) {
                if (point.y < lastClickPoint.y) {
                    transformer.zoom(95.0);
                    redraw = true;
                } else if (point.y > lastClickPoint.y) {
                    transformer.zoom(105.0);
                    redraw = true;
                }
	    // If only the left button, rotate around At point.
	    } else if (lMouseDown) {
                double rightDegrees = 0;
                double upDegrees = 0;
		
                if (point.x < lastClickPoint.x - 1) {
                    rightDegrees = (double)(point.x - lastClickPoint.x);
    		} else if (point.x > lastClickPoint.x + 1) {
                    rightDegrees = (double)(point.x - lastClickPoint.x);
                }
                if (point.y < lastClickPoint.y - 1) {
                    upDegrees = (double) (lastClickPoint.y - point.y);
	    	} else if (point.y > lastClickPoint.y + 1) {
                    upDegrees = (double) (lastClickPoint.y - point.y);
                }
                transformer.rotate(upDegrees, rightDegrees);
                redraw = true;
            }
        // If we're not viewing, we're in control mode. If we have
        // have selected a point, we need to move the point around.
        } else if ((controlStyle == CONTROL_EDITING) && (selectedPoint != -1)) {
            int xDiff = point.x - lastClickPoint.x; // Positive indicates rightward movement.
            int yDiff = point.y - lastClickPoint.y; // Positive indicates downward movement.
            RoffVector3D towardFrom = (transformer.getFrom()).sub(transformer.getAt()); // Positive is toward from
            RoffVector3D horizon = towardFrom.cross(transformer.getUp()); // Positive is left.
            RoffVector3D centerLine = horizon.cross(towardFrom); // Positive is up
            RoffVector3D change;

            if (lMouseDown) {
                // Drag left or right or up or down.
                change = horizon.scalarMultiply(xDiff * -.01);
                change = change.add(centerLine.scalarMultiply(yDiff * -.01));
                shape3D.vertices[selectedPoint] = (shape3D.vertices[selectedPoint]).add(change);
                redraw = true;
            } else if (rMouseDown) {
                // Drag in and out.
                change = towardFrom.scalarMultiply(yDiff * .01);
                shape3D.vertices[selectedPoint] = (shape3D.vertices[selectedPoint]).add(change);
                redraw = true;
            }
            // Recalculate the normals and centroids for the triangles.
            if (redraw) {
                tmanager.rebuild();
            }
        }

        if (redraw) {
            buildStructures();
            buildBuffer();
            repaint();
	}

        // Save the current point for the next call.
        lastClickPoint = point;
    }

    public void mouseMoved(MouseEvent e) {
    }

    public void mouseClicked(MouseEvent e) {

        if (controlStyle == CONTROL_EDITING) {

            Point point = e.getPoint();

            // If we currently don't have a point selected, we need to
            // select one.
            if (selectedPoint == -1) {

                int closestIndex = -1;
                int closestDistance;
                int distance;
                Point convertedPoint = new Point();

                buildStructures();
    
                int totalVertices = shape3D.numVertices;
                if (totalVertices > 0) {
                    closestIndex = 0;
                    convertedPoint.x = convertedXPoints[closestIndex];
                    convertedPoint.y = convertedYPoints[closestIndex];
                    closestDistance = (((point.x - convertedPoint.x) *
                                        (point.x - convertedPoint.x)) +
		                       ((point.y - convertedPoint.y) *
    				        (point.y - convertedPoint.y))
	    	                      );
                    for (int i = 1;i < totalVertices;i++) {
                        convertedPoint.x = convertedXPoints[i];
                        convertedPoint.y = convertedYPoints[i];
                        distance = (((point.x - convertedPoint.x) *
                                     (point.x - convertedPoint.x)) +
			            ((point.y - convertedPoint.y) *
			             (point.y - convertedPoint.y))
                                   );
                        if (distance < closestDistance) {
                            closestIndex = i;
                            closestDistance = distance;
                        }
                    }
                }

                selectedPoint = closestIndex;
                // If a point is currently selected, just de-select it.
            } else {
                selectedPoint = -1;
            }
            buildBuffer();
            repaint();
        }
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
    }

    public void mousePressed(MouseEvent e) {
        lastClickPoint = e.getPoint();
    }

    public void mouseReleased(MouseEvent e) {
    }

    public void drawPostScript(PrintWriter out, int scale, int options) {

        int sizeX = scale;
        double aspect = transformer.getAspectRatio();
        int sizeY = (int)((double)scale / aspect);
        transformer.setScreenScale(sizeX, sizeY);
        buildStructures();

        int offsetY = -((sizeY - scale) / 2);
        int transformY = offsetY + sizeY;

        out.println("newpath");
        out.println("0 " + offsetY + " moveto");
        out.println(sizeX + " " + offsetY + " lineto");
        out.println(sizeX + " " + transformY + " lineto");
        out.println("0 " + transformY + " lineto");
        out.println("closepath");
        if (PS_NOBG != (options & PS_NOBG)) {
           out.println("gsave");
           out.println("clip");
           out.println("grestore");
           setPostScriptColor(out, bgColor, options);
           out.println("fill");
        } else {
            out.println("clip");
        }

        if (observing) {
            RoffVector3D LoS = observedPerspective.getAt().sub(observedPerspective.getFrom());
            LoS = LoS.scalarDivide(5.0);
            double lenD = LoS.getLength();
            double lenS = lenD * Math.tan(observedPerspective.getAngle() * Math.PI / 360.0);
            RoffVector3D rightDir = LoS.cross(observedPerspective.getUp());
            rightDir.normalize();
            rightDir = rightDir.scalarMultiply(lenS * observedPerspective.getAspectRatio());
            RoffVector3D topDir = rightDir.cross(LoS);
            topDir.normalize();
            topDir = topDir.scalarMultiply(lenS);
            RoffLine2D line = new RoffLine2D();

            // Need the observed At, From, topLeft, topRight, bottomLeft, bottomRight
            RoffVector3D filmCenter = observedPerspective.getFrom().add(LoS);
            RoffVector3D topLeft3D = (filmCenter.add(topDir)).sub(rightDir);
            RoffVector3D topRight3D = (filmCenter.add(topDir)).add(rightDir);
            RoffVector3D bottomLeft3D = (filmCenter.sub(topDir)).sub(rightDir);
            RoffVector3D bottomRight3D = (filmCenter.sub(topDir)).add(rightDir);
            RoffVector3D oAt = observedPerspective.getAt();
            RoffVector3D oFrom = observedPerspective.getFrom();
            RoffVector3D oUp = observedPerspective.getUp();
            oUp = oUp.scalarMultiply(lenS);
            oUp = oUp.add(oAt);

            // Draw the line of sight
            GeneralPath gp = null;
            transformer.clipToLine(oAt, oFrom, line);
            if (line.valid) {
                out.println("newpath");
                out.println(line.startX + " " + line.startY + " moveto");
                out.println(line.endX + " " + line.endY + " lineto");
                setPostScriptColor(out, Color.white, options);
                out.println("stroke");
            }

            // Draw the Up vector at the At point.
            transformer.clipToLine(oAt, oUp, line);
            if (line.valid) {
                out.println("newpath");
                out.println(line.startX + " " + line.startY + " moveto");
                out.println(line.endX + " " + line.endY + " lineto");
                setPostScriptColor(out, Color.gray, options);
                out.println("stroke");
            }

            // Draw bulk of the viewing pyramid
            out.println("newpath");
            transformer.clipToLine(topLeft3D, bottomLeft3D, line);
            if (line.valid) {
                out.println(line.startX + " " + line.startY + " moveto");
                out.println(line.endX + " " + line.endY + " lineto");
            }

            transformer.clipToLine(bottomLeft3D, oFrom, line);
            if (line.valid) {
                out.println(line.startX + " " + line.startY + " moveto");
                out.println(line.endX + " " + line.endY + " lineto");
            }

            transformer.clipToLine(oFrom, bottomRight3D, line);
            if (line.valid) {
                out.println(line.startX + " " + line.startY + " moveto");
                out.println(line.endX + " " + line.endY + " lineto");
            }

            transformer.clipToLine(bottomRight3D, topRight3D, line);
            if (line.valid) {
                out.println(line.startX + " " + line.startY + " moveto");
                out.println(line.endX + " " + line.endY + " lineto");
            }

            transformer.clipToLine(bottomLeft3D, bottomRight3D, line);
            if (line.valid) {
                out.println(line.startX + " " + line.startY + " moveto");
                out.println(line.endX + " " + line.endY + " lineto");
            }

            setPostScriptColor(out, Color.red, options);
            out.println("stroke");

            // Draw the top triangle of the viewing pyramid
            out.println("newpath");
            transformer.clipToLine(oFrom, topLeft3D, line);
            if (line.valid) {
                out.println(line.startX + " " + line.startY + " moveto");
                out.println(line.endX + " " + line.endY + " lineto");
            }

            transformer.clipToLine(topLeft3D, topRight3D, line);
            if (line.valid) {
                out.println(line.startX + " " + line.startY + " moveto");
                out.println(line.endX + " " + line.endY + " lineto");
            }

            transformer.clipToLine(oFrom, topRight3D, line);
            if (line.valid) {
                out.println(line.startX + " " + line.startY + " moveto");
                out.println(line.endX + " " + line.endY + " lineto");
            }

            setPostScriptColor(out, Color.green, options);
            out.println("stroke");
        }


        if (viewStyle == VIEW_WIREFRAME || viewStyle == VIEW_WIREFRAME_AA) {
            out.println("newpath");
            setPostScriptColor(out, illuminator.getOutsideColor(), options);
            for (int i = 0; i < numLines2D; i++) {
                if (lines2D[i].valid) {
                    out.println(lines2D[i].startX + " " + (transformY - lines2D[i].startY) + " moveto");
                    out.println(lines2D[i].endX + " " + (transformY - lines2D[i].endY) + " lineto");
                }
            }
            out.println("stroke");
	} else if (viewStyle == VIEW_SOLID) {
	    int numTriangles = tmanager.getNumberOfTriangles();
	    RoffTriangle tri;
            Polygon poly;
            illuminator.setWorldLight(transformer.EyeToWorld(illuminator.getLightDirection()));
	    for (int i = 0;i < numTriangles;i++ ){
                tri = tmanager.get(i);
                if (tri.getDepth() > cullDepth) {
                    poly = tmanager.getPolygon(i);
                    out.println("newpath");    
                    //set the color to illuminator.Illuminate(tri->getNormal(), tri->getView());
                    setPostScriptColor(out, illuminator.illuminate(tri.getNormal(), tri.getView()), options);
                    out.println(poly.xpoints[0] + " " + (transformY - poly.ypoints[0]) + " moveto");
                    out.println(poly.xpoints[1] + " " + (transformY - poly.ypoints[1]) + " lineto");
                    out.println(poly.xpoints[2] + " " + (transformY - poly.ypoints[2]) + " lineto");
                    out.println("closepath");
                    out.println("gsave");
                    out.println("fill");
                    out.println("grestore");
                    out.println("stroke");
                }
	    }
	} else if (viewStyle == VIEW_WIREFRAME_NOHIDDENLINES || viewStyle == VIEW_WIREFRAME_NOHIDDENLINES_AA) {
            Color fillColor = bgColor;
            if (PS_NOBG == (options & PS_NOBG)) {
                fillColor = Color.white;
            }
	    int numTriangles = tmanager.getNumberOfTriangles();
	    RoffTriangle tri;
            Polygon poly;
            Rectangle bounds;
            Rectangle outerBounds = new Rectangle(-500, -500, sizeX + 500, sizeY + 500);
            illuminator.setWorldLight(transformer.EyeToWorld(illuminator.getLightDirection()));
	    for (int i = 0;i < numTriangles;i++ ){
                tri = tmanager.get(i);
                if (tri.getDepth() > cullDepth) {
                    poly = tmanager.getPolygon(i);
                    out.println("newpath");    
                    setPostScriptColor(out, fillColor, options);
                    out.println(poly.xpoints[0] + " " + (transformY - poly.ypoints[0]) + " moveto");
                    out.println(poly.xpoints[1] + " " + (transformY - poly.ypoints[1]) + " lineto");
                    out.println(poly.xpoints[2] + " " + (transformY - poly.ypoints[2]) + " lineto");
                    out.println("closepath");
                    out.println("gsave");
                    out.println("fill");
                    out.println("grestore");
                    bounds = poly.getBounds();
                    if (outerBounds.contains(bounds)) {
                        setPostScriptColor(out, illuminator.illuminate(tri.getNormal(), tri.getView()), options);
                        out.println("stroke");
                    }
                }
	    }
	} else if (viewStyle == VIEW_POINTS) {
		// Draw all the points in the array.
		int totalVertices = shape3D.numVertices;
                int xPoint;
                int yPoint;
                double radius = scale / 1000;
                out.println("newpath");    
                setPostScriptColor(out, illuminator.getOutsideColor(), options);

		for (int i = 0;i < totalVertices;i++) {
                    xPoint = convertedXPoints[i];
                    yPoint = convertedYPoints[i];
                    if (xPoint > 0 && yPoint > 0 && xPoint < sizeX && yPoint < sizeY) {
                        out.println((xPoint + radius) + " " + (transformY - yPoint) + " moveto");
                        out.println(xPoint + " " + (transformY - yPoint) + " " + radius + " 0 360 arc");
                    }
		}
                out.println("stroke");
        } 
        // Clean up after ourselves for the screen display.
        transformer.setScreenScale(currentSize.width, currentSize.height);
        buildStructures();
    }

    private void setPostScriptColor(PrintWriter out, Color theColor, int options) {
        float[] comps = theColor.getRGBColorComponents(null);
        if (PS_GRAY == (options & PS_GRAY)) {
            out.println(comps[0] * 0.3 + comps[1] * 0.59 + comps[2] * 0.11 + " setgray");
        } else {
            out.println(comps[0] + " " + comps[1] + " " + comps[2] + " setrgbcolor");
        }
    }
}